{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# Использование Tool от LlamaIndex вместе с ConversationalAgent\n",
    "1. Устанавливаем нужные пакеты"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "!pip install llama_index wikipedia gigachain openai\n",
    "!pip uninstall -y langchain"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "2. Загружаем статью из Википедии для поиска по ней\n",
    "3. Инициализируем VectorStoreIndex где будут храниться семантические данные по статье"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "from llama_index import VectorStoreIndex, download_loader\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = \"<Ваш OpenAI ключ>\"\n",
    "\n",
    "WikipediaReader = download_loader(\"WikipediaReader\")\n",
    "\n",
    "loader = WikipediaReader()\n",
    "\n",
    "documents = loader.load_data(\n",
    "    pages=[\"Московский метрополитен\"], auto_suggest=False, lang=\"ru\"\n",
    ")\n",
    "\n",
    "index = VectorStoreIndex.from_documents(documents)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "4. Инициализируем промпты для работы LLamaIndex + GigaChat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from langchain.chat_models import GigaChat\n",
    "from llama_index import ServiceContext\n",
    "from llama_index.llms import ChatMessage, LangChainLLM, MessageRole\n",
    "from llama_index.prompts import ChatPromptTemplate\n",
    "\n",
    "chat_text_qa_msgs = [\n",
    "    ChatMessage(\n",
    "        role=MessageRole.SYSTEM,\n",
    "        content=\"\"\"Ответь на вопрос, только если в контексте есть информация, чтобы ответить на вопрос.\n",
    "         Если в контексте нет информации отвечай, что не знаешь ответа.\n",
    "         Не упоминай, что у тебя есть контекст\"\"\",\n",
    "    ),\n",
    "    ChatMessage(\n",
    "        role=MessageRole.USER,\n",
    "        content=(\n",
    "            \"Используя только контекст и не свои знания ниже дай ответ на вопрос.\\n\"\n",
    "            \"---------------------\\n\"\n",
    "            \"{context_str}\\n\"\n",
    "            \"---------------------\\n\"\n",
    "            \"Дай ответ исходя из контекста и не из своих знаний на следующий вопрос: {query_str}\\n\"\n",
    "            # noqa: E501\n",
    "        ),\n",
    "    ),\n",
    "]\n",
    "text_qa_template = ChatPromptTemplate(chat_text_qa_msgs)\n",
    "\n",
    "# Refine Prompt\n",
    "chat_refine_msgs = [\n",
    "    ChatMessage(\n",
    "        role=MessageRole.SYSTEM,\n",
    "        content=\"\"\"Ответь на вопрос, только если в контексте есть информация, чтобы ответить на вопрос.\n",
    "         Если в контексте нет информации отвечай, что не знаешь ответа.\n",
    "         Не упоминай, что у тебя есть контекст\"\"\",\n",
    "    ),\n",
    "    ChatMessage(\n",
    "        role=MessageRole.USER,\n",
    "        content=(\n",
    "            \"У нас есть возможность улучить оригинальный ответ (если это нужно) \"\n",
    "            \"с контекстной информацией ниже\\n\"\n",
    "            \"------------\\n\"\n",
    "            \"{context_msg}\\n\"\n",
    "            \"------------\\n\"\n",
    "            \"Учитывая новый контекст, улучши ответ, \"\n",
    "            \"чтобы лучше ответить на оригинальный вопрос: {query_str}. \"\n",
    "            \"Если контекст бесполезен, выведи исходный ответ\\n\"\n",
    "            \"Исходный ответ: {existing_answer}\"\n",
    "        ),\n",
    "    ),\n",
    "]\n",
    "refine_template = ChatPromptTemplate(chat_refine_msgs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "5. Создаем QueryEngine в LLamaIndex, который будет общаться с помощью промптов выше, получать ответ по типу Question-Answer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "llm = GigaChat(\n",
    "    # Тут данные для входа, для этого примера я использовал 70b модель\n",
    ")\n",
    "\n",
    "lama_llm = LangChainLLM(llm=llm)\n",
    "service_context = ServiceContext.from_defaults(llm=lama_llm)\n",
    "\n",
    "query_engine = index.as_query_engine(\n",
    "    text_qa_template=text_qa_template,\n",
    "    refine_template=refine_template,\n",
    "    service_context=service_context,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "6. Инициализируем LLamaIndex Tool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from llama_index.langchain_helpers.agents import IndexToolConfig, LlamaIndexTool\n",
    "\n",
    "tool_config = IndexToolConfig(\n",
    "    query_engine=query_engine,\n",
    "    name=\"Vector Index\",\n",
    "    description=\"\"\"Описание: используется, когда тебе нужно узнать информацию про метро\n",
    "    Параметры: строка для поиска (передавай полностью, то что написал пользователь в Question)\"\"\",\n",
    "    tool_kwargs={\"return_direct\": True},\n",
    ")\n",
    "\n",
    "tool = LlamaIndexTool.from_tool_config(tool_config)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import re\n",
    "from typing import Union\n",
    "\n",
    "from langchain.agents.conversational.output_parser import ConvoOutputParser\n",
    "from langchain.schema import AgentAction, AgentFinish, OutputParserException\n",
    "\n",
    "\n",
    "def parse(self, text: str) -> Union[AgentAction, AgentFinish]:\n",
    "    text = re.sub(r\"Observation:.*\", \"\", text, 0, re.MULTILINE | re.DOTALL)\n",
    "    if f\"{self.ai_prefix}:\" in text:\n",
    "        return AgentFinish(\n",
    "            {\"output\": text.split(f\"{self.ai_prefix}:\")[-1].strip()}, text\n",
    "        )\n",
    "    regex = r\"Action: (.*?)[\\n]*Action Input: (.*)\"\n",
    "    match = re.search(regex, text)\n",
    "    if not match:\n",
    "        raise OutputParserException(f\"Could not parse LLM output: `{text}`\")\n",
    "    action = match.group(1)\n",
    "    action_input = match.group(2)\n",
    "    return AgentAction(action.strip(), action_input.strip(\" \").strip('\"'), text)\n",
    "\n",
    "\n",
    "ConvoOutputParser.parse = parse"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "7. Создаем Conversation агент, который сможет общаться с пользователем и, если поймет что это нужно, производить поиск по документам и генерировать ответь с помощью LLamaIndex"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from langchain.agents import initialize_agent\n",
    "from langchain.memory import ConversationBufferMemory\n",
    "\n",
    "# set Logging to DEBUG for more detailed outputs\n",
    "memory = ConversationBufferMemory(memory_key=\"chat_history\")\n",
    "agent_executor = initialize_agent(\n",
    "    [tool], llm, agent=\"conversational-react-description\", memory=memory, verbose=True\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3mThought: Мне нужно использовать функцию? Нет\n",
      "AI: Привет! Я — GigaChat, русскоязычный виртуальный помощник. Чем я могу вам помочь сегодня?\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": "'Привет! Я — GigaChat, русскоязычный виртуальный помощник. Чем я могу вам помочь сегодня?'"
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent_executor.run(\"Привет, ты кто?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3mThought: Мне нужно использовать функцию? Да\n",
      "Action: Vector Index\n",
      "Action Input: \"Мосметротур\"\n",
      "\u001b[0m\n",
      "Observation: \u001b[36;1m\u001b[1;3mМосметротур — это проект Городского экскурсионного бюро и Московского метрополитена, который предлагает экскурсии по метрополитену. Гиды проходят обучение и получают информацию об истории развития метро, правилах пользования и эксплуатации метрополитена, технике безопасности и порядке работы в случае чрезвычайных происшествий. Программа включает в себя около 15 экскурсий, включая экскурсии по публично доступным станциям.\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": "'Мосметротур — это проект Городского экскурсионного бюро и Московского метрополитена, который предлагает экскурсии по метрополитену. Гиды проходят обучение и получают информацию об истории развития метро, правилах пользования и эксплуатации метрополитена, технике безопасности и порядке работы в случае чрезвычайных происшествий. Программа включает в себя около 15 экскурсий, включая экскурсии по публично доступным станциям.'"
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent_executor.run(\"Расскажи про мосметротур\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
